#define GLEW_STATIC
#define _USE_MATH_DEFINES

#include <GL/glew.h>

#include <string.h>
#include <SDL/SDL.h>

#include <iostream>

#include <cmath>

#include <SOIL/SOIL.h>
//SOIL for image loading

#include <vectormath/scalar/cpp/vectormath_aos.h>

using namespace std;
using namespace Vectormath::Aos;

const double PI = std::atan(1.0)*4;

//shaders
const char* vertexSource =
	"#version 150\n"
	"in vec2 position;"
	"in vec3 color;"
	"in vec2 texcoord;"
	"out vec3 Color;"
	"out vec2 Texcoord;"
	"uniform mat4 trans;"
	"void main() {"
	"	Color = color;"
	"	Texcoord = texcoord;"
	"	gl_Position = trans * vec4( position, 0.0, 1.0 );"
	"}";
const char* fragmentSource =
	"#version 150\n"
	"in vec3 Color;"
	"in vec2 Texcoord;"
	"out vec4 outColor;"
	"uniform sampler2D texKitten;"
	"uniform sampler2D texPuppy;"
	"uniform float time;"
	"void main() {"
	"	float factor = ( sin( time * 3.0 ) + 1.0 ) / 2.0;"
	"	vec4 colKitten = texture(texKitten, Texcoord);"
	"	vec4 colPuppy = texture(texPuppy, Texcoord);"
	"	outColor = mix( colKitten, colPuppy, factor);"
	"}";

/*
 * This checks the key input and gives the keys functions
 */
void checkKeys()
{

}

int main( int argc, char *argv[] )
{
	SDL_Init(SDL_INIT_VIDEO);

	SDL_Surface* surface = //area (surface) to draw to
			SDL_SetVideoMode(800, 600, 32, //resX, resY, bitdepth
					SDL_HWSURFACE | SDL_DOUBLEBUF | SDL_OPENGL );
					//hardware acc, back buff, GL capable
					//for Fullscreen | SDL_FULLSCREEN );
	SDL_WM_SetCaption("Game Window", 0);//set window name

	glewExperimental = GL_TRUE;
	glewInit();
	GLenum err = glewInit();
	if (GLEW_OK != err)
	{
	  /* Problem: glewInit failed, something is seriously wrong. */
	  fprintf(stderr, "Error: %s\n", glewGetErrorString(err));
	}
	fprintf(stdout, "Status: Using GLEW %s\n", glewGetString(GLEW_VERSION));

	if (GLEW_VERSION_1_3)
	{
		if (GLEW_VERSION_1_5)
			{
			if (GLEW_VERSION_2_0)
				{
				if (GLEW_VERSION_3_1)
					{
					if (GLEW_VERSION_4_1)
						{
							cout << "OpenGL 4.1 supported" << endl;
						}else{
							cout << "OpenGL 4.1 not supported" << endl;
						}
						cout << "OpenGL 3.1 supported" << endl;
					}else{
						cout << "OpenGL 3.1 not supported" << endl;
					}
					cout << "OpenGL 2.0 supported" << endl;
				}else{
					cout << "OpenGL 2.0 not supported" << endl;
				}
				cout << "OpenGL 1.5 supported" << endl;
			}else{
				cout << "OpenGL 1.5 not supported" << endl;
			}
		cout << "OpenGL 1.3 supported" << endl;
	}else{
		cout << "OpenGL 1.3 not supported" << endl;
	}

	//create a vertex array object
	GLuint vao;
	glGenVertexArrays( 1, &vao );

	//bind it
	glBindVertexArray( vao );

	GLuint vbo;
	glGenBuffers( 1, &vbo );//generate 1 buffer

	GLuint vertexBuffer;
	glGenBuffers( 1, &vertexBuffer );

	printf( "%u\n", vertexBuffer );

	float vertices[] = {
	//  Position      Color             Texcoords
	    -0.5f,  0.5f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, // Top-left
	     0.5f,  0.5f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // Top-right
	     0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, // Bottom-right
	    -0.5f, -0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f  // Bottom-left
	};

	glBindBuffer( GL_ARRAY_BUFFER, vbo );//upload buffer to GPU

	glBufferData( GL_ARRAY_BUFFER, sizeof( vertices ), vertices, GL_STATIC_DRAW );//put vertex data in

	GLuint ebo;
	glGenBuffers( 1, &ebo );

	GLuint elements[] = {
		0, 1, 2,
		2, 3, 0
	};

	glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, ebo );
	glBufferData( GL_ELEMENT_ARRAY_BUFFER,
		sizeof( elements ), elements, GL_STATIC_DRAW );

	GLuint tex;
	glGenTextures( 1, &tex );

	glBindTexture( GL_TEXTURE_2D, tex); //2D array of pixels

	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT );
	//location (s,t,r), GL /_REPEAT /_MIRRORED_REPEAT /_CLAMP_TO_EDGE /_CLAMP_TO_BORDER

	float color[] = { 1.0f, 0.0f, 0.0f, 1.0f };
	glTexParameterfv( GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, color );
	//sets border to red

	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
	// sets filtering type: LINEAR-blur // NEAREST-sharp

	//glGenerateMipmap( GL_TEXTURE_2D );
	//generate a mipmap

//	// Black/white checkerboard
//	float pixels[] = {
//	    0.0f, 0.0f, 0.0f,   1.0f, 1.0f, 1.0f,
//	    1.0f, 1.0f, 1.0f,   0.0f, 0.0f, 0.0f
//	};
//	glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, 2, 2, 0, GL_RGB, GL_FLOAT, pixels );
//	//(2d array pixels, detail(0 is base), pixel format,
//	//	width, height, always 0, format of array, format of array, target array)


	GLuint vertexShader = glCreateShader( GL_VERTEX_SHADER );
	glShaderSource( vertexShader, 1, &vertexSource, NULL );//load the shader
	glCompileShader( vertexShader );//compile the shader

	GLint status;
	glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &status);
	if(status == GL_TRUE){
		cout << "vShader compile successful" << endl;
		cout << "no, really" << endl;
		char logBuffer[512];
		glGetShaderInfoLog( vertexShader, 512, NULL, logBuffer );
		cout << logBuffer << endl;
	}else{
		cout << "vShader compile failure" << endl;
		char logBuffer[512];
		glGetShaderInfoLog( vertexShader, 512, NULL, logBuffer );
		cout << logBuffer << endl;
	}

	//same for fragment shader
	cout << "pre-frag shader" << endl;
	GLuint fragmentShader = glCreateShader( GL_FRAGMENT_SHADER );
	glShaderSource( fragmentShader, 1, &fragmentSource, NULL );
	glCompileShader( fragmentShader );

	glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &status);
	if(status == GL_TRUE){
		cout << "fShader compile successful" << endl;
		char logBuffer[512];
		glGetShaderInfoLog( fragmentShader, 512, NULL, logBuffer );
		cout << logBuffer << endl;
	}else{
		cout << "fShader compile failure" << endl;
		char logBuffer[512];
		glGetShaderInfoLog( fragmentShader, 512, NULL, logBuffer );
		cout << logBuffer << endl;
	}

	//connect the 2 shaders into 1 program
	GLuint shaderProgram = glCreateProgram();
	glAttachShader( shaderProgram, vertexShader );
	glAttachShader( shaderProgram, fragmentShader );

	//specify which buffers to write output to
	glBindFragDataLocation( shaderProgram, 0, "outColor" );

	//link the shaders together
	glLinkProgram( shaderProgram );

	//call shader program
	glUseProgram( shaderProgram );

	//get a reference to 'position' in the v shader
	GLint posAttrib = glGetAttribLocation( shaderProgram, "position" );

	//enable the array
	glEnableVertexAttribArray( posAttrib );

	//specify how the data is retrieved from the array
	glVertexAttribPointer( posAttrib, 2, GL_FLOAT, GL_FALSE, 7*sizeof(float), 0 );
	//(input, # of values for input, type of input, normalize?, stride, offset)
	//stride - # of bytes between each position attribute in the array
	//offset - # of bytes the attribute starts after in the array

	//get a reference to 'color' in the v shader
	GLint colAttrib = glGetAttribLocation( shaderProgram, "color" );
	glEnableVertexAttribArray( colAttrib );
	glVertexAttribPointer( colAttrib, 3, GL_FLOAT, GL_FALSE, 7*sizeof(float), (void*)(2*sizeof(float)) );

	GLint texAttrib = glGetAttribLocation( shaderProgram, "texcoord" );
	glEnableVertexAttribArray( texAttrib );
	glVertexAttribPointer( texAttrib, 2, GL_FLOAT, GL_FALSE,
	                       7*sizeof(float), (void*)( 5*sizeof(float) ) );

	// Load textures
	GLuint textures[2];
	glGenTextures( 2, textures );

	int width, height;
	unsigned char* image;

	glActiveTexture( GL_TEXTURE0 );
	glBindTexture( GL_TEXTURE_2D, textures[0] );
		image = SOIL_load_image( "Assets/sample.png", &width, &height, 0, SOIL_LOAD_RGB );
		glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image );
		SOIL_free_image_data( image );
	glUniform1i( glGetUniformLocation( shaderProgram, "texKitten" ), 0 );

	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );

	glActiveTexture( GL_TEXTURE1 );
	glBindTexture( GL_TEXTURE_2D, textures[1] );
		image = SOIL_load_image( "Assets/sample2.png", &width, &height, 0, SOIL_LOAD_RGB );
		glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image );
		SOIL_free_image_data( image );
	glUniform1i( glGetUniformLocation( shaderProgram, "texPuppy" ), 1 );

	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );

	GLint uniTime = glGetUniformLocation( shaderProgram, "time" );

	Matrix4 trans;
	trans = Matrix4::rotation(PI, Vector3(0.0f, 0.0f, 1.0f));


	Vector4 result = trans * Vector4(1.0f, 0.0f, 0.0f, 1.0f);
	printf( "%f, %f, %f\n", result.getX(), result.getY(), result.getZ() );

	GLint uniTrans = glGetUniformLocation( shaderProgram, "trans" );
	GLfloat transmat [16] = {
	    trans.getElem(0,0), trans.getElem(0,1), trans.getElem(0,2), trans.getElem(0,3),
	    trans.getElem(1,0), trans.getElem(1,1), trans.getElem(1,2), trans.getElem(1,3),
	    trans.getElem(2,0), trans.getElem(2,1), trans.getElem(2,2), trans.getElem(2,3),
	    trans.getElem(3,0), trans.getElem(3,1), trans.getElem(3,2), trans.getElem(3,3) };
	glUniformMatrix4fv( uniTrans, 1, GL_FALSE, transmat );

	SDL_Event event;
	while ( true )//game loop
	{
		int i;
		i+=1;
		glUniform1f( uniTime, (float)i / (float)6000 );

		glDrawElements( GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
		//(primitive, # indices to draw, type of element data, offset)


	    while ( SDL_PollEvent( &event ) )//poll keyboard
	    {
	    	switch (event.type){//switch for event type
	    	case SDL_MOUSEMOTION://mouse moves
	    		break;
	    	case SDL_MOUSEBUTTONUP://mouse button up
	    		break;
	    	case SDL_MOUSEBUTTONDOWN://mouse button down
	    		break;
	    	case SDL_QUIT://corner close button clicked
	    		cout << "Windows close pressed" << endl;
	    		return 0;//quit
	    	case SDL_USEREVENT:
	    		break;
	    	case SDL_KEYUP://keyboard key up
	    		break;
	    	case SDL_KEYDOWN://keyboard key down
	    		switch(event.key.keysym.sym){
					case SDLK_ESCAPE:
						cout << "Escape pressed" << endl;
						return 0;//quit
					case SDLK_w:
						cout << "W key pressed" << endl;
						break;
					case SDLK_a:
						cout << "A key pressed" << endl;
						break;
					case SDLK_s:
						cout << "S key pressed" << endl;
						break;
					case SDLK_d:
						cout << "D key pressed" << endl;
						break;
	    		break;
	    		}
	    	}
		}
	    checkKeys();
	    SDL_GL_SwapBuffers();//swap the front buffer to the back buffer
	}
	SDL_Quit();//quit
    return 0;//quit
}
